package io.imp.orm.dao;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Hibernate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import io.imp.orm.Condition;
import io.imp.orm.Order;
import io.imp.orm.Pager;
import io.imp.orm.PagerRecords;
import io.imp.orm.Sort;
import io.imp.orm.utils.ConditionUtils;

/**
 * @ClassName: BaseJpaDao
 * @Description: TODO
 * @author CHEN-HF
 * @date 2017年12月14日 上午10:21:41
 * 
 */
public class BaseJpaDao<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements JpaDao<T, ID> {

	private Log logger = LogFactory.getLog(BaseJpaDao.class);
	private EntityManager entityManager;

	public BaseJpaDao(Class<T> domainClass, EntityManager em) {
		super(domainClass, em);
		this.entityManager = em;
	}

	@Override
	@Transactional
	public void delete(List<ID> ids) {
		deleteInBatch(findAll(ids));
	}

	@Override
	public T findOne(ID id) {
		return null;
	}

	@Override
	public List<T> findAll(Iterable<ID> iterable) {
		return null;
	}

	@Override
	public <S extends T> List<S> save(Iterable<S> iterable) {
		return null;
	}

	@Override
	public void delete(ID id) {

	}

	@Override
	@Transactional
	public void delete(String property, String value) {
		deleteInBatch(findList(property, value));
	}

	@Override
	@SuppressWarnings("unchecked")
	public T findOneInitialize(ID id, String[] cAttributes) {
		Object object = findOne(id);
		initializeObjectCollections(object, cAttributes);
		return (T) object;
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public T findOneByProperty(final String propertyName, final Object value) {
		return (T) super.findOne(new Specification() {
			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
				Condition condtion = ConditionUtils.getCondition(propertyName, Condition.EQUALS, value);
				return condtion.generateExpression(root, cb);
			}
		});
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public T findOneByProperties(final String[] properties, final Object[] values) {
		if ((properties == null) || (values == null) || (properties.length != values.length)) {
			return null;
		}
		return (T) super.findOne(new Specification() {
			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
				List predicates = new ArrayList();
				for (int i = 0; i < properties.length; i++) {
					Condition condtion = ConditionUtils.getCondition(properties[i], Condition.EQUALS, values[i]);
					predicates.add(condtion.generateExpression(root, cb));
				}
				return cb.and((Predicate[]) predicates.toArray(new Predicate[predicates.size()]));
			}
		});
	}

	@Override
	@SuppressWarnings("unchecked")
	public T findOneByProperty(String propertyName, Object value, String[] cAttributes) {
		Object entity = findOneByProperty(propertyName, value);
		initializeObjectCollections(entity, cAttributes);
		return (T) entity;
	}

	@Override
	public boolean exists(ID id) {
		return false;
	}

	@Override
	@SuppressWarnings("rawtypes")
	public boolean exists(String propertyName, Object value) {
		List entitys = findList(propertyName, value.toString());
		return entitys.size() > 0;
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List<T> findList(final String propertyName, final Object value) {
		return super.findAll(new Specification() {
			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
				Condition condtion = ConditionUtils.getCondition(propertyName, Condition.EQUALS, value.toString());
				return condtion.generateExpression(root, cb);
			}

		});
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List<T> findList(final String[] properties, final Object[] values) {
		if ((properties == null) || (values == null) || (properties.length != values.length)) {
			return null;
		}
		return super.findAll(new Specification() {
			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
				List predicates = new ArrayList();
				for (int i = 0; i < properties.length; i++) {
					Condition condtion = ConditionUtils.getCondition(properties[i], Condition.EQUALS, values[i]);
					predicates.add(condtion.generateExpression(root, cb));
				}
				return cb.and((Predicate[]) predicates.toArray(new Predicate[predicates.size()]));
			}
		});
	}

	@Override
	@SuppressWarnings("unchecked")
	public List<T> findStartByTermOnAOrB(String propertyA, String propertyB, String term) {
		String queryString = "from " + getEntityName() + " as model where x." + propertyA + " like :term1 or x."
				+ propertyB + " like :term2 order by x." + propertyA;

		Query queryObject = this.entityManager.createQuery(queryString);
		queryObject.setParameter("term1", term + "%");
		queryObject.setParameter("term2", "%" + term + "%");
		return queryObject.getResultList();
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List<T> commonQuery(final Collection<Condition> conditions, Collection<Order> orders) {
		return super.findAll(new Specification() {
			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
				List predicates = new ArrayList();
				if (conditions != null) {
					for (Condition condition : conditions) {
						predicates.add(condition.generateExpression(root, cb));
						predicates.add(condition.generateExpression(root, cb));
					}
				}
				return cb.and((Predicate[]) predicates.toArray(new Predicate[predicates.size()]));
			}
		}, buildSort(orders));
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public PagerRecords findByPager(Pager pager, final Collection<Condition> conditions, Collection<Order> orders) {
		Page page = super.findAll(new Specification() {
			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
				List predicates = new ArrayList();
				for (Condition condition : conditions) {
					predicates.add(condition.generateExpression(root, cb));
				}
				return cb.and((Predicate[]) predicates.toArray(new Predicate[predicates.size()]));
			}
		}, buildPageRequest(pager, orders));

		PagerRecords PagerRecords = new PagerRecords(page.getContent(), page.getTotalElements());
		return PagerRecords;
	}
	
	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public long count(final Collection<Condition> conditions) {
		long count = super.count(new Specification() {
			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
				List predicates = new ArrayList();
				for (Condition condition : conditions) {
					predicates.add(condition.generateExpression(root, cb));
				}
				return cb.and((Predicate[]) predicates.toArray(new Predicate[predicates.size()]));
			}
		});

		return count;
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public T findFirstByProperty(String property, Object value) {
		List result = findList(property, value.toString());
		if (result.size() > 0) {
			return (T) result.get(0);
		}
		return null;
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public T findFirstByProperties(String[] properties, Object[] values) {
		List result = findList(properties, values);
		if (result.size() > 0) {
			return (T) result.get(0);
		}
		return null;
	}

	@Override
	@Transactional
	public void update(String propertyName, Object oldValue, Object newValue) {
		StringBuffer queryBuf = new StringBuffer();
		queryBuf.append("update ").append(getEntityName()).append(" as x ").append(" set x.").append(propertyName)
				.append("=:newValue");
		if (oldValue != null) {
			queryBuf.append(" where x.").append(propertyName).append(" = ").append(" :oldValue");
		}

		Query query = this.entityManager.createQuery(queryBuf.toString()).setParameter("newValue", newValue);
		if (oldValue != null) {
			query.setParameter("oldValue", oldValue);
		}
		query.executeUpdate();
	}

	@Override
	public final void initializeObjectCollections(Object initializeObject, String[] cAttributes) {
		if ((initializeObject != null) && (cAttributes != null))
			for (String initializeCollection : cAttributes)
				try {
					initializeObjectCollection(initializeObject, initializeCollection);
				} catch (Exception e) {
					e.printStackTrace();
					this.logger.warn("属性【" + initializeCollection + "】延迟加载失败：" + e.getMessage(), e);
				}
	}

	private void initializeObjectCollection(Object initializeObject, String initializeCollection) throws Exception {
		Object obj = null;
		if ((initializeObject == null) || (initializeCollection == null) || (initializeCollection.equals(""))) {
			return;
		}
		// TODO
		// obj = PropertyUtils.getProperty(initializeObject,
		// initializeCollection);
		Hibernate.initialize(obj);
	}

	private Sort buildSort(Collection<Order> orders) {
		Sort sort = null;
		if ((orders != null) && (orders.size() > 0)) {
			sort = new Sort((Sort.Order[]) orders.toArray(new Order[orders.size()]));
		}
		return sort;
	}

	private PageRequest buildPageRequest(Pager pager, Collection<Order> orders) {
		Sort sort = buildSort(orders);

		if (pager.getPage() != 0) {
			pager.setPage(pager.getPage() - 1);
		}
		return new PageRequest(pager.getPage(), pager.getLimit(), sort); //layui
		
//		if (pager.getLimit() == null) { // adminlte table
//			if (pager.getPage() != 0) {
//				pager.setPage(pager.getPage() - 1);
//			}
//			return new PageRequest(pager.getPage(), pager.getRows(), sort);
//		} else { // easyui table
//			
//			return new PageRequest(pager.getOffset()/pager.getLimit(), pager.getLimit(), sort);
//		}

	}

	@Override
	public String getEntityName() {
		return getDomainClass().getSimpleName();
	}

	@Override
	public EntityManager getEntityManager() {
		return this.entityManager;
	}
}
